---
title: API 라우트를 사용하여 양식 작성
description: JavaScript를 사용하여 양식 제출을 API 경로로 보내는 방법을 알아보세요.
i18nReady: true
type: recipe
---
import { Steps } from '@astrojs/starlight/components';
import UIFrameworkTabs from "~/components/tabs/UIFrameworkTabs.astro";
import PackageManagerTabs from "~/components/tabs/PackageManagerTabs.astro";

HTML 양식을 사용하면 브라우저가 페이지를 새로 고치거나 새 페이지로 이동합니다. 대신 양식 데이터를 API 엔드포인트로 보내려면 JavaScript를 사용하여 양식 제출을 가로채야 합니다.

이 레시피는 양식 데이터를 API 엔드포인트로 보내고 해당 데이터를 처리하는 방법을 보여줍니다.

## 전제 조건

- [요청 시 렌더링을 위한 어댑터](/ko/guides/on-demand-rendering/)를 사용하는 프로젝트가 필요합니다.
- 설치된 [UI 프레임워크 통합](/ko/guides/framework-components/)이 필요합니다.

## 레시피

<Steps>
1. 양식 데이터를 수신할 `/api/feedback`에 `POST` API 엔드포인트를 생성합니다. 처리하려면 `request.formData()`를 사용하세요. 사용하기 전에 양식 값의 유효성을 검사하세요.

    이 예시에서는 메시지와 함께 JSON 객체를 클라이언트에 다시 보냅니다.

    ```ts title="src/pages/api/feedback.ts" "request.formData()" "post"
    export const prerender = false; // 'server' 모드에서는 필요하지 않습니다.
    import type { APIRoute } from "astro";

    export const POST: APIRoute = async ({ request }) => {
      const data = await request.formData();
      const name = data.get("name");
      const email = data.get("email");
      const message = data.get("message");
      // 데이터 유효성을 검사합니다. 아마도 이보다 더 많은 작업을 수행하고 싶을 것입니다.
      if (!name || !email || !message) {
        return new Response(
          JSON.stringify({
            message: "Missing required fields",
          }),
          { status: 400 }
        );
      }
      // 데이터로 작업을 수행한 다음 성공 응답을 반환합니다.
      return new Response(
        JSON.stringify({
          message: "Success!"
        }),
        { status: 200 }
      );
    };
    ```

2. UI 프레임워크를 사용하여 양식 컴포넌트를 만듭니다. 각 입력에는 해당 입력의 값을 설명하는 `name` 속성이 있어야 합니다.

    양식을 제출하려면 `<button>` 또는 `<input type="submit">` 요소를 포함해야 합니다.

    <UIFrameworkTabs>
      <Fragment slot="preact">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="react">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="solid">
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="svelte">
        ```svelte title="src/components/FeedbackForm.svelte"
        <form>
          <label>
            Name
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Email
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Message
            <textarea id="message" name="message" required />
          </label>
          <button>Send</button>
        </form>
        ```
      </Fragment>
      <Fragment slot="vue">
        ```vue title="src/components/FeedbackForm.vue"
        <template>
          <form>
            <label>
              Name
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Email
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Message
              <textarea id="message" name="message" required />
            </label>
            <button>Send</button>
          </form>
        </template>
        ```
      </Fragment>

    </UIFrameworkTabs>


3. 제출 이벤트를 받아들이는 함수를 만든 다음 이를 `submit` 핸들러로 양식에 전달합니다.

    함수에서는 다음을 수행합니다.
    - 이벤트에서 `preventDefault()`를 호출하여 브라우저의 기본 제출 프로세스를 재정의합니다.
    - `FormData` 객체를 생성하고 `fetch()`를 사용하여 `POST` 요청으로 엔드포인트에 보냅니다.
  
    <UIFrameworkTabs>
      <Fragment slot="preact">
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 4-17, 34} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { useState } from "preact/hooks";

        export default function Form() {
          const [responseMessage, setResponseMessage] = useState("");

          async function submit(e: SubmitEvent) {
            e.preventDefault();
            const formData = new FormData(e.target as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            if (data.message) {
              setResponseMessage(data.message);
            }
          }

          return (
            <form onSubmit={submit}>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
              {responseMessage && <p>{responseMessage}</p>}
            </form>
          );
        }

        ```
      </Fragment>
      <Fragment slot="react">
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1-2, 5-18, 35} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { useState } from "react";
        import type { FormEvent } from "react";

        export default function Form() {
          const [responseMessage, setResponseMessage] = useState("");

          async function submit(e: FormEvent<HTMLFormElement>) {
            e.preventDefault();
            const formData = new FormData(e.target as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            if (data.message) {
              setResponseMessage(data.message);
            }
          }

          return (
            <form onSubmit={submit}>
              <label htmlFor="name">
                Name
                <input type="text" id="name" name="name" autoComplete="name" required />
              </label>
              <label htmlFor="email">
                Email
                <input type="email" id="email" name="email" autoComplete="email" required />
              </label>
              <label htmlFor="message">
                Message
                <textarea id="message" name="message" autoComplete="off" required />
              </label>
              <button>Send</button>
              {responseMessage && <p>{responseMessage}</p>}
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="solid">
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 3-9, 13-19, 36} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { createSignal, createResource, Suspense } from "solid-js";

        async function postFormData(formData: FormData) {
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          return data;
        }

        export default function Form() {
          const [formData, setFormData] = createSignal<FormData>();
          const [response] = createResource(formData, postFormData);

          function submit(e: SubmitEvent) {
            e.preventDefault();
            setFormData(new FormData(e.target as HTMLFormElement));
          }

          return (
            <form onSubmit={submit}>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
              <Suspense>{response() && <p>{response().message}</p>}</Suspense>
            </form>
          );
        }

        ```
      </Fragment>
      <Fragment slot="svelte">
        ```svelte title="src/components/FeedbackForm.svelte" "/api/feedback" add={1-14, 30-32} add="on:submit={submit}" "formData" "e.preventDefault();"
        <script lang="ts">
          let responseMessage: string;

          async function submit(e: SubmitEvent) {
            e.preventDefault();
            const formData = new FormData(e.currentTarget as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            responseMessage = data.message;
          }
        </script>

        <form on:submit={submit}>
          <label>
            Name
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Email
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Message
            <textarea id="message" name="message" required />
          </label>
          <button>Send</button>
          {#if responseMessage}
            <p>{responseMessage}</p>
          {/if}
        </form>
            ```
      </Fragment>
      <Fragment slot="vue">
        ```vue title="src/components/FeedbackForm.vue" "/api/feedback" add={1-16, 33} "formData" "e.preventDefault();"
        <script setup lang="ts">
        import { ref } from "vue";

        const responseMessage = ref<string>();

        async function submit(e: Event) {
          e.preventDefault();
          const formData = new FormData(e.currentTarget as HTMLFormElement);
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          responseMessage.value = data.message;
        }
        </script>

        <template>
          <form @submit="submit">
            <label>
              Name
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Email
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Message
              <textarea id="message" name="message" required />
            </label>
            <button>Send</button>
            <p v-if="responseMessage">{{ responseMessage }}</p>
          </form>
        </template>
        ```
      </Fragment>

    </UIFrameworkTabs>

4. `<FeedbackForm />` 컴포넌트를 가져와 페이지에 포함합니다. 양식 로직이 원할 때 수화되도록 하려면 `client:*` 지시어를 사용하세요.
    
    <UIFrameworkTabs>
        <Fragment slot="preact">
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="react">
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="solid">
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="svelte">
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm.svelte"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="vue">
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm.vue"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
    </UIFrameworkTabs>
</Steps>

{/* ## Extension: Use Zod to validate your form

[Zod form data](https://www.npmjs.com/package/zod-form-data) builds on top of [Zod](https://github.com/colinhacks/zod) to validate your form using a schema. This simplifies your code, as it allows you to declare the fields and their requirements, and let Zod handle the validation.

1. Install `zod` and `zod-form-data`.

    <PackageManagerTabs>
      <Fragment slot="npm">
        ```shell
          npm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="pnpm">
        ```shell
          pnpm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="yarn">
        ```shell
          yarn add zod zod-form-data
        ```
      </Fragment>
    </PackageManagerTabs>

2. In your API Route file, declare your schema using `zfd.formData` and export it. */}
